<?php

/**
 * 功能类：文件类型缓存类
 * 属于：内置默认缓存
 * @category   Think
 * @package  Think
 * @subpackage  Util
 * @author    liu21st <liu21st@gmail.com>
 * @version   $Id: CacheFile.class.php 2791 2012-02-29 10:08:57Z liu21st
 */
class CacheFile extends Cache {

    /**
     * 缓存存储前缀
     * @var string
     * @access protected
     */
    protected $prefix='~@';

    /**
     * 构造函数，初始化options设置
     * @access public
     */
    public function __construct($options='') {
        if(!empty($options)) {             //如果传入$options，将变量赋值options属性
            $this->options =  $options;
        }
        //传入的$options为空使用默认的方式,temp采用默认的路径，expire,默认是0，长度默认也是0
        $this->options['temp'] = !empty($options['temp'])?$options['temp']:C('DATA_CACHE_PATH');
        $this->options['expire'] = isset($options['expire'])?$options['expire']:C('DATA_CACHE_TIME');
        $this->options['length']  =  isset($options['length'])?$options['length']:0;
        //文件路径中最后没有/需要添加"/"斜杠
        if(substr($this->options['temp'], -1) != '/')    $this->options['temp'] .= '/';
        $this->init();
        //判断是否存在目录，文件是否可写
        $this->connected = is_dir($this->options['temp']) && is_writeable($this->options['temp']);

    }

    /**
     * 初始化文件缓存目录创建
     * @access private
     * @return boolen
     */
    private function init() {
        $stat = stat($this->options['temp']); //stat能够返回文件信息
        $dir_perms = $stat['mode'] & 0007777; // 使用mode字串与007777位运算得到权限值=十进制哦
        // 创建项目缓存目录
        if (!is_dir($this->options['temp'])) { //目录不存在
            if (!mkdir($this->options['temp'])) //创建目录并判断是否层高
            {
             chmod($this->options['temp'], sprintf("%04d", $dir_perms)); //给权限的时候需要八进制记得补0哦，才行。
                mkdir($this->options['temp']);
            }
        }
    }

    /**
     * 是否连接---->文件是否可读可写
     * @access public
     * @return boolen
     */
    private function isConnected() {
        return $this->connected;
    }

    /**
     * 将变量的处理成保存的文件名
     * @access private
     * @param string $name 缓存变量名
     * @return string
     */
    private function filename($name) {
        $name	=	md5($name);            //对变量名md5加密
        if(C('DATA_CACHE_SUBDIR')) {  //配置中使用子目录来创建缓存
            // 使用子目录
            $dir   ='';
            for($i=0;$i<C('DATA_PATH_LEVEL');$i++) {//子目录级别
                $dir	.=	$name{$i}.'/';
            }
            if(!is_dir($this->options['temp'].$dir)) {
                mk_dir($this->options['temp'].$dir);
            }
            $filename	=	$dir.$this->prefix.$name.'.php';
        }else{
            $filename	=	$this->prefix.$name.'.php';
        }
        return $this->options['temp'].$filename;
    }

    /**
     * 读取缓存
     * @access public
     * @param string $name 缓存变量名
     * @return mixed
     */
    public function get($name) {
        $filename   =   $this->filename($name);       //根据变量名得到文件全路径
        if (!$this->isConnected() || !is_file($filename)) {     //确保文件可读可写，以及文件存在
           return false;
        }
        N('cache_read',1);                               //记录读次数  一个或多个s(?)
        $content    =   file_get_contents($filename);            //获取文件内容
        if( false !== $content) {                                //获取到内容
            /* 例子：$content="<?php//\n000000000000s:12:\"caosdfsdfsdf\";?>"; \n也算一个字符*/
            $expire  =  (int)substr($content,8, 12);  //12位时间 例子3600s  000000003600
            if($expire != 0 && time() > filemtime($filename) + $expire) {//如果设置了时间期限，当前时间>上次修改时间+过期时间

                unlink($filename);                             //删除过期的缓存文件
                return false;
            }
            if(C('DATA_CACHE_CHECK')) {//开启数据校验
                $check  =  substr($content,20, 32); //md5一般是32位,截取出来
                $content   =  substr($content,52, -3);/*8(<php...)+12(00000..)+32(md5) 去除尾部"\n?>" 截取后边我们想要的内容*/
                if($check != md5($content)) {//将字符串中md5与内容md5进行校验
                    return false;
                }
            }else {
            	$content   =  substr($content,20, -3); //没有开启校验直接截取内容
            }
            if(C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) {  //如果启用数据压缩

                $content   =   gzuncompress($content);            //解压缩内容
            }
            $content    =   unserialize($content);                //进行反序列化
            return $content;
        }
        else {
            return false;
        }
    }

    /**
     * 将变量名对应值----写入规则文件缓存中
     * gzcompress：对字符串进行压缩
     * @access public
     * @param string $name 缓存变量名
     * @param mixed $value  存储数据
     * @param int $expire  有效时间 0为永久
     * @return boolen
     */
    public function set($name,$value,$expire=null) {
        N('cache_write',1);            //一次请求周期内缓存写的次数-一次多次s(?,?)
        if(is_null($expire)) {                 //有效时间为null使用默认，expire 0
            $expire =  $this->options['expire']; //设置有效时间并赋值
        }
        $filename   =   $this->filename($name);    //通过变量名得到文件名全路径
        $data   =   serialize($value);             //将存储数据进行序列化
        if( C('DATA_CACHE_COMPRESS') && function_exists('gzcompress')) { //是否进行数据压缩

            $data   =   gzcompress($data,3);  //对字符串进行压缩
        }
        if(C('DATA_CACHE_CHECK')) {        //开启数据校验将字符进行md5加密处理
            $check  =  md5($data);
        }else {
            $check  =  '';
        }

        $data    = "<?php\n//".sprintf('%012d',$expire).$check.$data."\n?>"; //设置写入的内容格式
        $result  =   file_put_contents($filename,$data);     //写入文件
        if($result) {
            if($this->options['length']>0) {        //要想限制缓存的个数，需要先设置length的长度大于0
                $this->queue($name);               // 通过队列将多出缓存删除，先入先出
            }
            clearstatcache();               //未防止文件状态信息改变，影响文件判断，清除文件缓存
            return true;
        }else {
            return false;                   //写入失败，false
        }
    }

    /**
     * 删除缓存--删除缓存文件
     * @access public
     * @param string $name 缓存变量名
     * @return boolen
     */
    public function rm($name) {
        return unlink($this->filename($name));
    }

    /**
     * 清除缓存
     * @access public
     * @param string $name 缓存变量名
     * @return boolen
     */
    public function clear() {
        $path   =  $this->options['temp'];          //获取缓存文件路径
        if ( $dir = opendir( $path ) ) {            //打开目录句柄
            while ( $file = readdir( $dir ) ) {     //读目录句柄中的内容
                $check = is_dir( $file );           //是文件目录
                if ( !$check )
                    unlink( $path . $file );  //不是目录是文件直接删除该文件
            }
            closedir( $dir );                        //关闭目录句柄
            return true;
        }
    }

}